/****************************************************************************
 *
 * Copyright (C) 2003-2008 Los Alamos National Security, LLC
 *                         Packet Analytics Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2 as
 * published by the Free Software Foundation.  You may not use, modify or
 * distribute this program under any other version of the GNU General
 * Public License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 ****************************************************************************/
package nfse;

import java.io.File;
import java.util.Vector;
import java.net.ServerSocket;

import javax.net.ServerSocketFactory;

import nfse.listener.NetListenerServer;
import nfse.query.QueryServer;
import nfse.result.ResultServer;
import nfse.stats.StatsServer;
import nfse.dataset.netflow.NetFlowThread;

/*******************************************************************************
 * Master application for running Net/FSE. <br>
 * <ul>
 * <li>Reads in configuration files</li>
 * <li>Starts various services:</li>
 * <ul>
 * <li>DataServer</li>
 * <li>QueryServer</li>
 * <li>ResultServer</li>
 * <li>AutoQuery</li>
 * <li>AutoMigrate</li>
 * <li>ResultServer</li>
 * </ul>
 * <li>Starts data collection mechanisms:</li>
 * <ul>
 * <li>NetFlow threads</li>
 * <li>Syslog threads</li>
 * </ul>
 * </ul>
 * 
 * @author bduphoff
 * 
 *         This code is the property of Los Alamos National Laboratory and the
 *         University of California, Copyright 2005
 */
public class NetFSEServer {

    private static Vector<SyslogThread> syslogThreads = new Vector<SyslogThread>();

    private static Vector<NetFlowThread> netflowThreads = new Vector<NetFlowThread>();

    private StatsServer statsServer = null;

   /***************************************************************************
     * Runs the NetFSE server according to the args parameter fields.
     * 
     * @param args
     *            Usage: config_file
     */
    public static void main(String[] args) {
        try {
        	
            NetFSEServer server = new NetFSEServer();

            NetFSEServer.NetFSEShutdown sh = server.new NetFSEShutdown();
            Runtime.getRuntime().addShutdownHook(sh);

            System.setProperty("java.protocol.handler.pkgs", "javax.net.ssl");

            if (args.length > 1) {
				System.out.println("Usage: java -jar nfse.jar [config_file]");
				return;
			}

			if (args.length == 1)
				NetFSE.CONFIG_FILE = args[0];

			String configPath = NetFSE.CONFIG_FILE;
			File config = new File(configPath);
			if (config.exists()) {
			    System.setProperty("nfse.configfile", configPath);
				NetFSE.configure();
			} else {
				throw (new Exception(
						"The specified configuration file does not exist: '"
								+ configPath + "'"));
			}

			String nodeConfigPath = NetFSE.NODE_CONFIG_FILE;
			File config2 = new File(nodeConfigPath);
			if (config2.exists()) {
				NetFSE.processNodeConfigFile(config2);
			} else {
				throw (new Exception(
						"The specified node configuration file does not exist: '"
								+ nodeConfigPath + "'"));
			}

            server.init();

            /*******************************************************************
             * Start the ControlSocket thread and wait for connection. Note: the
             * ControlSocket is not fully implemented at this time but may be in
             * a future release.
             */
            ServerSocketFactory ssf = ServerSocketFactory.getDefault();
            ServerSocket ss = ssf.createServerSocket(NetFSE.CONTROL_SERVER_PORT);
            while (true) {
                ControlSocket cs = new ControlSocket(ss.accept());
                cs.start();
                System.out.println("Created new control socket.");
            }
        } catch (Exception E) {
            E.printStackTrace();
        }
    }

    /***************************************************************************
     * Provides access to the stats server to other objects in the Net/FSE
     * environment.
     * 
     * @return The Net/FSE stats server, only if this is the master node
     *         (NetFSE.MASTER == true)
     */
    public StatsServer getStatsServer() {
        return statsServer;
    }

    public void init() {
        try {
            /*******************************************************************
             * Start the DataServer thread
             */
            DataServer ds = new DataServer(NetFSE.DATA_SERVER_PORT, this);
            ds.start();
            System.out.println("Started data server.");

            /*******************************************************************
             * Start the ResultServer thread
             */
            ResultServer rs = new ResultServer(NetFSE.RESULT_SERVER_PORT);
            rs.start();
            System.out.println("Started result server.");

            /*******************************************************************
             * Start the QueryServer thread
             */
            QueryServer qs = new QueryServer(NetFSE.QUERY_SERVER_PORT, rs);
            qs.start();
            System.out.println("Started query server.");

            if (NetFSE.NET_LISTENER) {
                /*******************************************************************
                 * Start the NetListenerServer thread
                 */
                NetListenerServer ns = new NetListenerServer(NetFSE.NET_LISTENER_SERVER_PORT);
                ns.start();
                System.out.println("Started Net/Listener server.");
                Thread.sleep(5000);
            } else {
                System.out.println("Net/Listener OFF.");
            }

            /***********************************************************
             * Start the AutoMigrate thread with defaults of 30 days and min 10
             * dynamic tables.
             * 
             * @TODO Change these values from hard-coded to configuration driven
             *       parameters.
             */
            if (NetFSE.AUTO_MIGRATE) {
                AutoMigrate am = new AutoMigrate(NetFSE.AUTO_MIGRATE_NUM_DAYS, NetFSE.AUTO_MIGRATE_MIN,
                        NetFSE.AUTO_MIGRATE_MAX);
                if (NetFSE.AUTO_MIGRATE_NUM_DELETE_DAYS > 0)
                    am.setNumDeleteDays(NetFSE.AUTO_MIGRATE_NUM_DELETE_DAYS);
                am.start();
                System.out.println("Started Auto Migrate.");
            } else {
                System.out.println("AutoMigrate is OFF");
            }
            
            statsServer = new StatsServer(NetFSE.STATS_SERVER_PORT);

            /*******************************************************************
             * The operations that follow are only executed by the master
             * Net/FSE server
             */
            if (NetFSE.MASTER) {

                if (NetFSE.STATS) {
                    /***************************************************************
                     * Start the stats server to collect statistical information
                     * on the data streams being processed by Net/FSE.
                     */
                    
                    statsServer.start();
                    System.out.println("Started stats server.");
                } else {
                    System.out.println("StatsServer is OFF");
                }

                /***************************************************************
                 * Open a connection to the metadata database.
                 */
                MetaDatabase mdb = MetaDatabase.create();

                if (NetFSE.SYSLOG) {
                    /***************************************************************
                     * Start the Syslog threads
                     */
                    SyslogThread[] syslogThreads = mdb.getSyslogThreads();
                    for (int i = 0; i < syslogThreads.length; i++) {
                        NetFSEServer.addSyslogThread(syslogThreads[i]);
                        syslogThreads[i].start();
                    }
                    System.out.println("Started Syslog listeners.");
                } else {
                    System.out.println("Syslog is OFF");
                }

                if (NetFSE.NET_FLOW) {
                    /***************************************************************
                     * Start the NetFlow threads
                     */
                    NetFlowThread[] netflowThreads = mdb.getNetFlowThreads();
                    for (int i = 0; i < netflowThreads.length; i++) {
                        NetFSEServer.addNetFlowThread(netflowThreads[i]);
                        netflowThreads[i].start();
                    }
                    System.out.println("Started NetFlow listeners.");
                } else {
                    System.out.println("NetFlow collection is OFF");
                }

                // Done with the metadata connection; close it
                mdb.closeConnection();

                if (NetFSE.AUTO_QUERY) {
                    /***********************************************************
                     * Start the auto query (Critical Servers) thread
                     */
                    AutoQuery aq = new AutoQuery();
                    aq.start();
                    System.out.println("Started Auto query.");
                } else {
                    System.out.println("AutoQuery is OFF");
                }

                if (NetFSE.TEMP_DB_DELETE) {
                    /***********************************************************
                     * Start the thread to delete stale search results from the
                     * results database
                     */
                    TempDBDelete del = new TempDBDelete();
                    del.start();
                    System.out.println("Started Temp Database Delete process.");
                } else {
                    System.out.println("TempDBDelete is OFF");
                }
            }

            Thread.sleep(2000);

            if (NetFSE.DATA_FILE_MONITOR) {
                DataFileMonitorLauncher launcher = new DataFileMonitorLauncher();
                launcher.start();
                System.out.println("Started Data File Monitor Launcher.");
            } else {
                System.out.println("Data File Monitor Launcher is OFF.");
            }

        } catch (Exception E) {
            E.printStackTrace();
        }
    }

    /***************************************************************************
     * Adds a newly defined syslog configuration to the system for future
     * reference. Called by the init() method.
     * 
     * @param syslogThread
     *            The SyslogThread object being added to the server
     */
    public static synchronized void addSyslogThread(SyslogThread syslogThread) {
        for (int i = 0; i < syslogThreads.size(); i++) {
            SyslogThread temp = (SyslogThread) syslogThreads.elementAt(i);
            if (temp.getSensorID() == syslogThread.getSensorID())
                return; // Don't add a thread twice
        }
        syslogThreads.addElement(syslogThread);
    }

    /***************************************************************************
     * Removes a syslog configuration to the system . This function is called by
     * the ControlSocket to shutdown syslog threads dynamically.
     * 
     * @param syslogThread
     *            The SyslogThread object being removed
     */
    public static synchronized SyslogThread removeSyslogThread(int sensorID) {
        for (int i = 0; i < 0; i++) {
            SyslogThread syslogThread = (SyslogThread) syslogThreads.elementAt(i);
            if (syslogThread.getSensorID() == sensorID) {
                syslogThreads.removeElementAt(i);
                syslogThread.quit();
                return syslogThread;
            }
        }
        /***********************************************************************
         * At this point the thread could not be found. An invalid sensorID or
         * no such configuration.
         */
        return null;
    }

    /***************************************************************************
     * Adds a newly defined NetFlow configuration to the system for future
     * reference. Called by the init() method.
     * 
     * @param syslogThread
     *            The NetFlowThread object being added to the server
     */
    public static synchronized void addNetFlowThread(NetFlowThread netflowThread) {
        for (int i = 0; i < netflowThreads.size(); i++) {
            NetFlowThread temp = (NetFlowThread) netflowThreads.elementAt(i);
            if (temp.getSensorID() == netflowThread.getSensorID())
                return; // Don't add a thread twice
        }
        netflowThreads.addElement(netflowThread);
    }

    /***************************************************************************
     * Removes a NetFlow configuration from the system . This function is called
     * by the ControlSocket to shutdown NetFlow threads dynamically.
     * 
     * @param netflowThread
     *            The NetFlowThread object being removed
     */
    public static synchronized NetFlowThread removeNetFlowThread(int sensorID) {
        for (int i = 0; i < 0; i++) {
            NetFlowThread netflowThread = (NetFlowThread) netflowThreads.elementAt(i);
            if (netflowThread.getSensorID() == sensorID) {
                netflowThreads.removeElementAt(i);
                netflowThread.quit();
                return netflowThread;
            }
        }
        /***********************************************************************
         * At this point the thread could not be found. An invalid sensorID or
         * no such configuration.
         */
        return null;
    }

    /***************************************************************************
     * Reads in the config file specified in the args
     * 
     * @param config
     *            The file to process
     * @exception Any
     *                processing error will generate an exception
     */
    public static void processConfigFile(File config) throws Exception {
        System.setProperty("nfse.configfile", config.getAbsolutePath());
        NetFSE.configure();
    }

    /***************************************************************************
     * This subclass is used to shutdown the Net/FSE server gracefully. The
     * primary concern is shutting down the Syslog and NetFlow threads. Future
     * versions should gracefully close any existing database connections and
     * wait for any in-process operations (e.g. migration processes).
     * 
     * @author bduphoff
     * 
     */
    public class NetFSEShutdown extends Thread {
        public void run() {

            // Close all syslog threads
            Vector<SyslogThread> syslogThreads = NetFSEServer.syslogThreads;
            for (int i = 0; i < syslogThreads.size(); i++) {
                SyslogThread thread = (SyslogThread) syslogThreads.elementAt(i);
                Process p = thread.quit();
                p.destroy();
                System.out.flush();
                System.err.flush();
            }

            // Close all NetFlow threads
            Vector<NetFlowThread> netflowThreads = NetFSEServer.netflowThreads;
            for (int i = 0; i < netflowThreads.size(); i++) {
                NetFlowThread thread = (NetFlowThread) netflowThreads.elementAt(i);
                thread.quit();
                System.out.flush();
                System.err.flush();
            }

        }
    }

}
